﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using QiHe.Yaml.Grammar;

using TracedReality.Primitive;

namespace TracedReality.RayTracing
{
    /// <summary>
    /// A scene to render
    /// </summary>
    public class Scene
    {
        /// <summary>
        /// A List of the shapes in the scene.
        /// </summary>
        public List<Shape> shapes;

        /// <summary>
        /// A List of the lights in the scene.
        /// </summary>
        public List<Light> lights;

        /// <summary>
        /// The camera for the scene.
        /// </summary>
        public Camera camera;

        /// <summary>
        /// Create an empty scene with no camera.
        /// </summary>
        public Scene()
        {
            shapes = new List<Shape>();
            lights = new List<Light>();
            camera = null;
        }

        /// <summary>
        /// Find the BoundingBox of this scene.
        /// Does not include infinite geometry.
        /// </summary>
        /// <returns>The BoundingBox for the scene.</returns>
        public BoundingBox getBoundingBox()
        {
            if (shapes.Count < 1) { return null; }

            Vector min = null;
            Vector max = null;

            foreach (Shape s in shapes)
            {
                if (s.bound.isFinite())
                {
                    min = Vector.Min(min, s.bound.min);
                    max = Vector.Max(max, s.bound.max);
                }
            }

            return new BoundingBox(min, max);
        }

        /// <summary>
        /// Add the given Shape to the scene.
        /// </summary>
        /// <param name="p">The Shape to add.</param>
        public void addShape(Shape p) { shapes.Add(p); }

        /// <summary>
        /// Add the given Light to the scene.
        /// </summary>
        /// <param name="l">The Light to add.</param>
        public void addLight(Light l) { lights.Add(l); }

        /// <summary>
        /// Set this Scene's camera to the given one.
        /// </summary>
        /// <param name="c">The camera to use.</param>
        public void setCamera(Camera c) { camera = c; }

        /// <summary>
        /// Get a string representation of the Scene.
        /// It should be possible to parse the Scene from this string.
        /// </summary>
        /// <returns>A string representing this Scene.</returns>
        public override string ToString()
        {
            if (camera == null || lights == null || shapes == null) { return ""; }

            string res = camera.ToString();

            foreach (Light l in lights) { res += "\n" + l; }

            foreach (Shape s in shapes) { res += "\n" + s; }

            return res;
        }

        #region Scene parsing
        public static Scene loadSceneFromFile(string file)
        {
            return parseScene(File.ReadAllText(file));
        }

        public static Scene parseScene(string sc)
        {
            Scene scene = new Scene();
            TextInput input = new TextInput(sc);

            bool success;
            YamlParser parser = new YamlParser();
            YamlStream stream = parser.ParseYamlStream(input, out success);

            if (!success) { MessageBox.Show(parser.GetErrorMessages()); throw new Exception("Failed to parse file"); }

            YamlDocument doc = stream.Documents[0];
            Mapping map = (Mapping)doc.Root;

            // Parse the settings first
            MappingEntry settings = map.Find("Settings");
            if (settings != null) { Settings.Parse(settings); }

            foreach (MappingEntry ent in map.Entries)
            {
                if (ent.Key.ToString() == "Settings") { continue; }

                try
                {
                    Type t = Type.GetType("TracedReality.Primitive." + ent.Key.ToString(), true);
                    object result = t.InvokeMember("Parse",
                            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod |
                            System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.DeclaredOnly,
                            null,
                            null,
                            new object[] { ent });

                    if (result is Shape) { scene.addShape((Shape)result); }
                    else if (result is Light) { scene.addLight((Light)result); }
                    else if (result is Camera) { scene.setCamera((Camera)result); }
                }
                catch (Exception e) { Console.WriteLine(e); }
            }

            return scene;
        }
        #endregion
    }
}
